/*******************************************************************************
 * Copyright (c) 2015 fortiss GmbH
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Waldemar Eisenmenger, Alois Zoitl - initial API and implementation and/or initial documentation
 *******************************************************************************/

#include "sysfsprocint.h"
#include "forte/util/devlog.h"
#include "forte/util/extevhandlerhelper.h"
#include "forte/util/criticalregion.h"
#include <string>

using namespace std::literals::string_literals;

const CIEC_STRING CSysFsProcessInterface::scmOK("OK"_STRING);
const CIEC_STRING CSysFsProcessInterface::scmPinInUse("Pin already in use by other FB"_STRING);
const CIEC_STRING CSysFsProcessInterface::scmNotInitialised("FB not initialized"_STRING);
const CIEC_STRING CSysFsProcessInterface::scmError("Error"_STRING);
const CIEC_STRING CSysFsProcessInterface::scmCouldNotRead("Could not read"_STRING);
const CIEC_STRING CSysFsProcessInterface::scmCouldNotWrite("Could not write"_STRING);

CSysFsProcessInterface::CSysFsProcessInterface(forte::CFBContainer &paContainer,
                                               const SFBInterfaceSpec &paInterfaceSpec,
                                               const forte::StringId paInstanceNameId) :
    CProcessInterfaceBase(paContainer, paInterfaceSpec, paInstanceNameId) {
  mFile.rdbuf()->pubsetbuf(nullptr, 0); // disable buffer to avoid latency
  STATUS() = scmNotInitialised;
}

CSysFsProcessInterface::~CSysFsProcessInterface() {
  unexportIO(); // Will unexport everything, so next time FORTE starts it won't fail to initialize.
}

bool CSysFsProcessInterface::setDirection(bool paIsInput) {
  bool retVal = false;
  std::string fileName = "/sys/class/gpio/gpio"s + static_cast<std::string>(PARAMS()) + "/direction"s;
  std::ofstream mDirectionFile;
  mDirectionFile.open(fileName.c_str());
  if (mDirectionFile.is_open()) {
    if (paIsInput) {
      mDirectionFile << "in";
    } else {
      mDirectionFile << "out";
    }
    if (!mDirectionFile.fail()) {
      retVal = true;
    } else {
      DEVLOG_ERROR("[CSysFsProcessInterface::setDirection] Error writing to file %s.\n", fileName.c_str());
    }
  } else {
    DEVLOG_ERROR("[CSysFsProcessInterface::setDirection] Opening file %s failed.\n", fileName.c_str());
  }

  return retVal;
}

bool CSysFsProcessInterface::exportGPIO() {
  bool retVal = false;
  std::string fileName = "/sys/class/gpio/export";
  std::ofstream mExportFile;
  mExportFile.open(fileName.c_str());
  if (mExportFile.is_open()) {
    mExportFile << PARAMS().c_str();
    if (!mExportFile.fail()) {
      retVal = true;
    } else {
      DEVLOG_ERROR("[CSysFsProcessInterface::exportGPIO] Error writing to file %s.\n", fileName.c_str());
    }
    mExportFile.close();
    retVal = true;
  } else {
    DEVLOG_ERROR("[CSysFsProcessInterface::exportGPIO] Opening file %s failed.\n", fileName.c_str());
  }

  return retVal;
}

bool CSysFsProcessInterface::valueGPIO(bool paIsInput) {
  bool retVal = false;
  std::string fileName = "/sys/class/gpio/gpio"s + static_cast<std::string>(PARAMS()) + "/value"s;
  if (paIsInput) {
    mFile.open(fileName.c_str(), std::fstream::in);
  } else {
    mFile.open(fileName.c_str(), std::fstream::out);
  }

  if (mFile.is_open()) {
    retVal = true;
  } else {
    DEVLOG_ERROR("[CSysFsProcessInterface::valueGPIO] Opening file %s failed.\n", fileName.c_str());
  }
  return retVal;
}

bool CSysFsProcessInterface::initialise(bool paIsInput, CEventChainExecutionThread *const) {
  bool retVal = false;
  if (exportGPIO()) {
    CThread::sleepThread(250);
    if (setDirection(paIsInput)) {
      CThread::sleepThread(250);
      if (valueGPIO(paIsInput)) {
        if (paIsInput) {
          getExtEvHandler<CIOHandler>(*this).registerIXFB(this);
          if (!getExtEvHandler<CIOHandler>(*this).isAlive()) {
            getExtEvHandler<CIOHandler>(*this).start();
          }
        }
        DEVLOG_DEBUG("[CSysFsProcessInterface::initialise] Pin with PARAM() %s was properly initialized.\n",
                     PARAMS().getValue());
        STATUS() = scmOK;
        retVal = true;
      }
    }
  }

  return retVal;
}

bool CSysFsProcessInterface::unexportIO() {
  bool retVal = false;
  std::string fileName = "/sys/class/gpio/unexport"s;
  std::ofstream mUnExport;

  mFile.close();
  mUnExport.open(fileName.c_str(), std::fstream::out);
  if (mUnExport.is_open()) {
    mUnExport << PARAMS().c_str();
    if (!mUnExport.fail()) {
      retVal = true;
      STATUS() = scmOK;
    } else {
      STATUS() = scmError;
      DEVLOG_ERROR("[CSysFsProcessInterface::deinitialise] Error writing PARAMS() to file %s.\n", fileName.c_str());
    }
  } else {
    STATUS() = scmError;
    DEVLOG_ERROR("[CSysFsProcessInterface::deinitialise] Opening file %s failed.\n", fileName.c_str());
  }
  return retVal;
}

bool CSysFsProcessInterface::deinitialise() {
  getExtEvHandler<CIOHandler>(*this).unregisterIXFB(this);
  return unexportIO();
}

bool CSysFsProcessInterface::readPin() {
  return true;
}

bool CSysFsProcessInterface::checkInputData() {
  bool retVal = false;
  if (mFile.is_open()) {
    char binData = 0;
    mFile.clear();
    mFile.seekg(0, std::ios::beg);
    mFile.read(&binData, 1);
    if (mFile.fail()) {
      STATUS() = scmCouldNotRead;
    } else {
      bool newData = '0' != binData;
      if (newData != IN_X()) {
        IN_X() = CIEC_BOOL(newData);
        retVal = true;
      }
    }
  } else {
    STATUS() = scmNotInitialised;
  }

  return retVal;
}

bool CSysFsProcessInterface::writePin() {
  bool retVal = false;
  if (mFile.is_open()) {
    mFile.clear();
    mFile.seekp(0, std::ios::beg);
    unsigned int val = (false != OUT_X()) ? 1 : 0; // if true set the led to full glowing
    mFile << val;
    if (!mFile.fail()) {
      STATUS() = scmOK;
      retVal = true;
    } else {
      DEVLOG_ERROR("[CSysFsProcessInterface::writePin] Could not write %u to output file\n", val);
      STATUS() = scmCouldNotWrite;
    }
  } else {
    DEVLOG_ERROR(
        "[CSysFsProcessInterface::writePin] Cannot write to output since the FB was not properly initialized\n");
    STATUS() = scmNotInitialised;
  }

  return retVal;
}

DEFINE_HANDLER(CSysFsProcessInterface::CIOHandler);

CSysFsProcessInterface::CIOHandler::CIOHandler(CDeviceExecution &paDeviceExecution) :
    CExternalEventHandler(paDeviceExecution) {
}

CSysFsProcessInterface::CIOHandler::~CIOHandler() {
  CCriticalRegion readList(mReadFBListSync);
  mReadFBList.clear();
  end();
}

void CSysFsProcessInterface::CIOHandler::registerIXFB(CSysFsProcessInterface *paFB) {
  CCriticalRegion readList(mReadFBListSync);
  mReadFBList.push_back(paFB);
}

void CSysFsProcessInterface::CIOHandler::unregisterIXFB(CSysFsProcessInterface *paFB) {
  CCriticalRegion readList(mReadFBListSync);
  mReadFBList.erase(std::remove(mReadFBList.begin(), mReadFBList.end(), paFB), mReadFBList.end());
}

void CSysFsProcessInterface::CIOHandler::run() {
  while (isAlive()) {
    sleepThread(10);
    updateReadData();
  }
}

void CSysFsProcessInterface::CIOHandler::updateReadData() {
  CCriticalRegion readList(mReadFBListSync);
  for (CSysFsProcessInterface *it : mReadFBList) {
    if (it->checkInputData()) {
      startNewEventChain(it);
    }
  }
}

void CSysFsProcessInterface::CIOHandler::enableHandler() {
  // do nothing
}

void CSysFsProcessInterface::CIOHandler::disableHandler() {
  end();
}
